﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using JasLib.Extensions;

namespace JasLib
{
    /// <summary>
    /// This is necessary because the .NET Version and FileVersionInfo are inflexible.
    /// </summary>
    [Serializable]
    public class UniversalVersion
    {
        private int _iSubComponentPadding = 1;
        public int SubComponentPadding
        {
            get { return _iSubComponentPadding; }
            set { _iSubComponentPadding = value; }
        }

        public int Major
        {
            get { return this[0]; }
            set { this[0] = value; }
        }

        public int Minor
        {
            get { return this[1]; }
            set { this[1] = value; }
        }

        /// <summary>
        /// Indexed property that allows sparse get/set of any version component without fear of raising an exception.
        /// </summary>
        public int this[int iIndex]
        {
            get
            {
                /// Emulate missing components.
                if (iIndex >= _components.Count)
                    return 0;
                return _components[iIndex];
            }
            set
            {
                /// Pad out the components.
                while ((_components.Count - 1) < iIndex)
                    _components.Add(0);
                _components[iIndex] = value;
                return;
            }
        }

        private readonly List<int> _components = new List<int>();
        public IList<int> Components
        {
            get { return _components; }
        }

        public UniversalVersion()
        {
            return;
        }

        public UniversalVersion(string strVersion, int? iSubComponentPadding = 1)
        {
            if (!ParseToSelf(strVersion))
                throw new FormatException(string.Format("\"{0}\" is not a valid version format.", strVersion));

            if (iSubComponentPadding != null)
                _iSubComponentPadding = iSubComponentPadding.Value;
            return;
        }

        public UniversalVersion(int iSubComponentPadding, params int[] components)
        {
            _iSubComponentPadding = iSubComponentPadding;
            _components.AddRange(components);
            return;
        }

        public void Normalize(int iMinimumComponents = 0)
        {
            /// Remove trailing zero components.
            for (int iIndex = _components.Count - 1; iIndex >= 0 && _components[iIndex] == 0; iIndex--)
                _components.RemoveAt(iIndex);

            /// Pad trailing zero components.
            while (_components.Count < iMinimumComponents)
                _components.Add(0);
            //_components.AddRange(Enumerable.Repeat<int>(0, iMinimumComponents - _components.Count));

            return;
        }

        #region Conversion

        public static implicit operator UniversalVersion(string strVersion)
        {
            return new UniversalVersion(strVersion, 1);
        }

        public static implicit operator UniversalVersion(int iMajorVersion)
        {
            return new UniversalVersion(1, iMajorVersion);
        }

        public static implicit operator UniversalVersion(double dVersion)
        {
            return new UniversalVersion(dVersion.ToString(), 1);
        }

        public static implicit operator UniversalVersion(System.Version inputVersion)
        {
            UniversalVersion outputVersion = new UniversalVersion(1, inputVersion.Major, inputVersion.Minor, inputVersion.Build, inputVersion.Revision);
            return outputVersion;
        }

        public static implicit operator System.Version(UniversalVersion inputVersion)
        {
            System.Version outputVersion = new System.Version(inputVersion[0], inputVersion[1], inputVersion[2], inputVersion[3]);
            return outputVersion;
        }

        public static implicit operator UniversalVersion(System.Diagnostics.FileVersionInfo inputVersion)
        {
            UniversalVersion outputVersion = new UniversalVersion(inputVersion.FileVersion);
            return outputVersion;
        }

        public static bool TryParse(string strVersion, out UniversalVersion version)
        {
            version = new UniversalVersion();

            bool bSuccess = version.ParseToSelf(strVersion);
            if (!bSuccess)
                version = null;
            return bSuccess;
        }

        /// <summary>
        /// TODO: If a subcomponent has leading zeroes, save its length as the padding.
        /// </summary>
        protected bool ParseToSelf(string strVersion)
        {
            /// Allow empty entries.
            string[] stringComponents = strVersion.Trim().Split(".");

            for (int iIndex = 0; iIndex < stringComponents.Length; iIndex++)
            {
                /// If this field is empty, no big deal. "8..0" would be interpreted as "8.0.0".
                if (!string.IsNullOrEmpty(stringComponents[iIndex]))
                {
                    string strComponent = stringComponents[iIndex].Trim();

                    int iComponent;
                    if (!int.TryParse(strComponent, out iComponent))
                        return false;

                    this[iIndex] = iComponent;
                }
            }

            return true;
        }

        public override string ToString()
        {
            if (_components.Count == 0)
                return string.Empty;

            string strSubComponentPaddingFormat = "D" + _iSubComponentPadding.ToString();
            List<string> stringComponents = _components.ConvertAll(i => i.ToString(strSubComponentPaddingFormat));

            /// Overwrite the major version with the unpadded version.
            stringComponents[0] = _components[0].ToString();
            return stringComponents.Join(".");
        }

        #endregion

        #region Comparison

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public static bool operator >(UniversalVersion version1, UniversalVersion version2)
        {
            return (Compare(version1, version2) > 0);
        }

        public static bool operator <(UniversalVersion version1, UniversalVersion version2)
        {
            return (Compare(version1, version2) < 0);
        }

        public static bool operator ==(UniversalVersion version1, UniversalVersion version2)
        {
            return (Compare(version1, version2) == 0);
        }

        public static bool operator !=(UniversalVersion version1, UniversalVersion version2)
        {
            return !(version1 == version2);
        }

        public static int Compare(IList<int> version1Components, IList<int> version2Components)
        {
            int iMaxComponents;
            int iCountComparison;
            if (version1Components.Count > version2Components.Count)
            {
                iMaxComponents = version2Components.Count;
                iCountComparison = 1;
            }
            else if (version1Components.Count < version2Components.Count)
            {
                iMaxComponents = version1Components.Count;
                iCountComparison = -1;
            }
            else
            {
                iMaxComponents = version1Components.Count;
                iCountComparison = 0;
            }

            for (int iIndex = 0; iIndex < iMaxComponents; iIndex++)
            {
                int iComparison = version1Components[iIndex].CompareTo(version2Components[iIndex]);
                if (iComparison != 0)
                    return iComparison;
            }

            /// All else being equal, default to whoever has more components.
            return iCountComparison;
        }

        public static int Compare(UniversalVersion version1, UniversalVersion version2)
        {
            return Compare(version1._components, version2._components);
        }

        public static int Compare(string strVersion1, string strVersion2)
        {
            return Compare((UniversalVersion)strVersion1, (UniversalVersion)strVersion2);
        }

        public static int Compare(UniversalVersion version1, params int[] version2Components)
        {
            return Compare(version1._components, version2Components);
        }

        public bool MatchesWildcard(string strWildcardVersion, string strWildcardToken = "*")
        {
            /// Allow empty entries.
            string[] stringComponents = strWildcardVersion.Trim().Split(new string[] { "." }, StringSplitOptions.None);

            /// Parse the wildcard string.
            List<int?> wildcardComponents = stringComponents
                .Select(str => str.TrimToNull() ?? "0") /// Trim all strings (none will be null), convert empty strings to zero.
                .Select(str => str == strWildcardToken ? (int?)null : (int?)int.Parse(str)) /// Convert strings to nullable ints.
                .ToList();

            /// Compare the explicitly specified components.
            for (int iIndex = 0; iIndex < wildcardComponents.Count; iIndex++)
            {
                if (wildcardComponents[iIndex].HasValue)
                {
                    if (wildcardComponents[iIndex].Value != this[iIndex])
                        return false;
                }
            }

            if (_components.Count > wildcardComponents.Count)
            {
                /// Automatic success if the last wildcard element is null
                /// (i.e. everything at the end of the wildcard string was wildcarded).
                /// "8.*" is a match for "8.9.8.6.".
                if (wildcardComponents.Count > 0 && !wildcardComponents[wildcardComponents.Count - 1].HasValue)
                    return true;

                /// If the extra components are just zeroes, they're irrelevant.
                if (_components.Skip(wildcardComponents.Count).Any(i => i != 0))
                    return false;
            }

            return true;
        }

        #endregion
    }
}
